package com.chatplus.application.service.pay.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.chatplus.application.client.pay.domain.response.InternalPayQueryResponse;
import com.chatplus.application.client.pay.domain.response.InternalPayResponse;
import com.chatplus.application.common.lock.TLock;
import com.chatplus.application.common.lock.TurnRightLock;
import com.chatplus.application.common.logging.SouthernQuietLogger;
import com.chatplus.application.common.logging.SouthernQuietLoggerFactory;
import com.chatplus.application.common.util.IdGenerator;
import com.chatplus.application.common.util.PlusJsonUtils;
import com.chatplus.application.domain.entity.pay.PayRequestEntity;
import com.chatplus.application.enumeration.AppBusinessCodeEnum;
import com.chatplus.application.enumeration.PayChannelEnum;
import com.chatplus.application.enumeration.PayStatusEnum;
import com.chatplus.application.event.pay.PayFailEvent;
import com.chatplus.application.event.pay.PaySuccessEvent;
import com.chatplus.application.event.pay.dto.PayRequestEvent;
import com.chatplus.application.service.pay.PayChannelServiceProvider;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.Instant;

/**
 * 支付处理器
 */
@Service
public class PayProcessor {
    private static final SouthernQuietLogger LOGGER = SouthernQuietLoggerFactory.getLogger(PayProcessor.class);
    private final PayRequestServiceImpl payRequestService;
    private final TurnRightLock turnRightLock;
    private final ApplicationContext applicationContext;

    private final PayChannelServiceProvider payChannelServiceProvider;

    public PayProcessor(PayRequestServiceImpl payRequestService
            , TurnRightLock turnRightLock
            , ApplicationContext applicationContext
            , PayChannelServiceProvider payChannelServiceProvider) {
        this.payRequestService = payRequestService;
        this.turnRightLock = turnRightLock;
        this.applicationContext = applicationContext;
        this.payChannelServiceProvider = payChannelServiceProvider;
    }

    /**
     * 支付成功处理
     */
    @Transactional(rollbackFor = Exception.class)
    public void onPaySuccess(PayRequestEntity payRequestEntity) {
        Long payRequestId = payRequestEntity.getId();
        LambdaUpdateWrapper<PayRequestEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(PayRequestEntity::getId, payRequestId);
        updateWrapper.set(PayRequestEntity::getSuccessAt, payRequestEntity.getSuccessAt());
        updateWrapper.set(PayRequestEntity::getPayerInfo, payRequestEntity.getPayerInfo());
        updateWrapper.set(PayRequestEntity::getTradeTransactionId, payRequestEntity.getTradeTransactionId());
        updateWrapper.set(PayRequestEntity::getUpdatedAt, Instant.now());
        updateWrapper.set(PayRequestEntity::getPayStatus, PayStatusEnum.SUCCESS);
        updateWrapper.set(PayRequestEntity::getErrCode, payRequestEntity.getErrCode());
        updateWrapper.set(PayRequestEntity::getErrCodeDes, payRequestEntity.getErrCodeDes());
        if (payRequestEntity.getPaidMoney() != null) {
            updateWrapper.set(PayRequestEntity::getPaidMoney, payRequestEntity.getPaidMoney());
        } else {
            updateWrapper.set(PayRequestEntity::getPaidMoney, payRequestEntity.getTotalMoney());
        }
        updateWrapper.setSql(" scheme_version = scheme_version + 1 ");
        payRequestService.update(updateWrapper);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    publishEvent(payRequestId);
                }
            });
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void onPayFail(PayRequestEntity payRequestEntity) {
        if (payRequestEntity == null) {
            return;
        }
        Long payRequestId = payRequestEntity.getId();
        LambdaUpdateWrapper<PayRequestEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(PayRequestEntity::getId, payRequestId);
        updateWrapper.set(PayRequestEntity::getUpdatedAt, Instant.now());
        updateWrapper.set(PayRequestEntity::getPayStatus, PayStatusEnum.FAIL);
        updateWrapper.set(PayRequestEntity::getErrCode, payRequestEntity.getErrCode());
        updateWrapper.set(PayRequestEntity::getErrCodeDes, payRequestEntity.getErrCodeDes());
        payRequestService.update(updateWrapper);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    publishEvent(payRequestId);
                }
            });
        }
    }

    private void publishEvent(Long payRequestId) {
        PayRequestEntity payRequestEntity = payRequestService.getById(payRequestId);
        PayRequestEvent payRequestEvent = new PayRequestEvent();
        payRequestEvent.setOrderId(payRequestEntity.getBizId());
        switch (payRequestEntity.getPayStatus()) {
            case SUCCESS:
                applicationContext.publishEvent(new PaySuccessEvent(payRequestEntity.getBizCode().name(), payRequestEvent));
                break;
            case FAIL:
                applicationContext.publishEvent(new PayFailEvent(payRequestEntity.getBizCode().name(), payRequestEvent));
                break;
            default:
                break;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public InternalPayResponse payHandle(Long orderId, Long userId, Long totalMoney, PayChannelEnum payChannel, String subject) {
        PayRequestEntity payRequestEntity = new PayRequestEntity();
        payRequestEntity.setTotalMoney(totalMoney);
        payRequestEntity.setBizCode(AppBusinessCodeEnum.CHAT);
        payRequestEntity.setPayChannel(payChannel);
        payRequestEntity.setBizId(orderId);
        payRequestEntity.setPayStatus(PayStatusEnum.NOT);
        payRequestEntity.setRequestAt(Instant.now());
        payRequestEntity.setUserId(userId);
        payRequestEntity.setPayTransactionId(IdGenerator.generateLongId());
        InternalPayResponse internalPayResponse = payChannelServiceProvider.getPayChannelService(payChannel).orderPay(payRequestEntity, subject);
        payRequestService.save(payRequestEntity);
        return internalPayResponse;
    }

    public boolean queryPayResult(Long payRequestId) {
        if (payRequestId == null) {
            return false;
        }
        //加锁
        String lockKey = "Key:Lock:QueryPayResult:" + payRequestId;
        PayRequestEntity payRequestEntity;
        try (TLock tLock = turnRightLock.tryLock(lockKey)) {
            if (tLock == null) {
                return false;
            }
            payRequestEntity = payRequestService.getById(payRequestId);
            if (payRequestEntity == null) {
                LOGGER.message("发起支付结果查询,支付请求不存在").context("payRequestId", payRequestId).warn();
                return false;
            }
            if (payRequestEntity.getPayStatus() == PayStatusEnum.FAIL) {
                return false;
            }
            if (payRequestEntity.getPayStatus() == PayStatusEnum.SUCCESS) {
                return true;
            }
            InternalPayQueryResponse paymentQueryResponse =
                    payChannelServiceProvider.getPayChannelService(payRequestEntity.getPayChannel())
                            .queryOrderPay(payRequestEntity);
            LOGGER.message("发起支付结果查询")
                    .context("payRequestEntity", payRequestEntity)
                    .context("response", PlusJsonUtils.toJsonString(paymentQueryResponse)).info();
            if (paymentQueryResponse == null || paymentQueryResponse.getPayStatusEnum() == null) {
                return false;
            }
            PayStatusEnum payStatus = paymentQueryResponse.getPayStatusEnum();
            PayProcessor payProcessor = (PayProcessor) AopContext.currentProxy();
            switch (payStatus) {
                case SUCCESS:
                    //3、修改支付结果
                    Instant paySuccessTime = paymentQueryResponse.getPaySuccessAt();
                    String payerInfo = paymentQueryResponse.getPayerInfo();
                    // 外部订单号
                    String tradeTransactionId = paymentQueryResponse.getTradeTransactionId();
                    payRequestEntity.setSuccessAt(paySuccessTime);
                    payRequestEntity.setPayerInfo(payerInfo);
                    payRequestEntity.setTradeTransactionId(tradeTransactionId);
                    payProcessor.onPaySuccess(payRequestEntity);
                    return true;
                case FAIL:
                    payProcessor.onPayFail(payRequestEntity);
                    return false;
                default:
                    return false;
            }
        } catch (Exception e) {
            LOGGER.message("查询支付结果报错")
                    .context("payRequestId", payRequestId)
                    .exception(e)
                    .error();
        }
        return false;
    }
}
